/*
 * Copyright (C) 2014 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.util;

import traderlib.core.data.types.Time;
import traderlib.core.data.types.Timestamp;
import traderlib.core.data.types.Date;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Locale;

/**
 * Utility class to convert from/to a String to/from the supported types.
 *
 * @author Miquel Sas
 */
public final class Convert {

	/**
	 * Special save chars for files encoded like property files.
	 */
	private static final String specialSaveChars = "=: \t\r\n\f#!";
	/**
	 * A table of hex digits
	 */
	private static final char[] hexDigit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

	/**
	 * Container for date format map items.
	 */
	private static class FormatItem {

		SimpleDateFormat format = null;
		String pattern = null;
	}

	/**
	 * A localized date format map.
	 */
	private static HashMap<Locale, FormatItem> dateFormatMap = null;
	/**
	 * A localized time format map.
	 */
	private static HashMap<Locale, FormatItem> timeFormatMap = null;
	/**
	 * A localized timestamp format map.
	 */
	private static HashMap<Locale, FormatItem> timestampFormatMap = null;

	private static String addSecondsText(String str) {
		String text = str;
		char sep = 0;
		for (int i = text.length() - 1; i >= 0; i--) {
			if (!Character.isDigit(text.charAt(i))) {
				sep = text.charAt(i);
				break;
			}
		}
		if (sep > 0) {
			text += sep + "00";
		}
		return text;
	}

	/**
	 * Convert from a <code>BigDecimal</code> forcing the scale.
	 *
	 * @return A string.
	 * @param number A number as a <code>java.math.BigDecimal</code>
	 * @param scale The scale.
	 * @param locale The desired locale.
	 */
	public static String fmtFromBigDecimal(BigDecimal number, int scale, Locale locale) {
		if (number == null) {
			return "";
		}
		NumberFormat formatted = NumberFormat.getNumberInstance(locale);
		formatted.setMaximumFractionDigits(scale);
		formatted.setMinimumFractionDigits(scale);
		return formatted.format(number.doubleValue());
	}

	/**
	 * Convert from a <code>BigDecimal</code> forcing the scale.
	 *
	 * @return A string.
	 * @param number A number as a <code>java.math.BigDecimal</code>
	 * @param scale The scale.
	 */
	public static String fmtFromBigDecimal(BigDecimal number, int scale) {
		return fmtFromBigDecimal(number, scale, Locale.getDefault());
	}

	/**
	 * Convert from a <code>BigDecimal</code>.
	 *
	 * @return A string.
	 * @param number The number to conver as a <code>BigDecimal</code>
	 * @param locale The appliable locale.
	 */
	public static String fmtFromBigDecimal(BigDecimal number, Locale locale) {
		if (number == null) {
			return "";
		}
		NumberFormat formatted = NumberFormat.getNumberInstance(locale);
		formatted.setMaximumFractionDigits(number.scale());
		formatted.setMinimumFractionDigits(number.scale());
		return formatted.format(number.doubleValue());
	}

	/**
	 * Convert from a <code>BigDecimal</code>.
	 *
	 * @return A string.
	 * @param number The number to conver as a <code>BigDecimal</code>
	 */
	public static String fmtFromBigDecimal(BigDecimal number) {
		return fmtFromBigDecimal(number, Locale.getDefault());
	}

	/**
	 * Returns the formatted string representation of a boolean.
	 *
	 * @param bool The boolean value
	 * @return The formatted string representation
	 */
	public static String fmtFromBoolean(boolean bool) {
		return fmtFromBoolean(bool, Locale.getDefault());
	}

	/**
	 * Returns the formatted string representation of a boolean.
	 *
	 * @param bool The boolean value
	 * @param locale The locale to use
	 * @return The formatted string representation
	 */
	public static String fmtFromBoolean(boolean bool, Locale locale) {
		if (bool) {
			return "True";
		} else {
			return "False";
		}
	}

	/**
	 * Convert from a <code>byte</code>.
	 *
	 * @return A string.
	 * @param num The <code>byte</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromByte(byte num, Locale loc) {
		return NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from a <code>byte</code>.
	 *
	 * @return A string.
	 * @param num The <code>byte</code> to convert.
	 */
	public static String fmtFromByte(byte num) {
		return fmtFromByte(num, Locale.getDefault());
	}

	/**
	 * Convert from a <code>char</code>.
	 *
	 * @return A string.
	 * @param chr The <code>char</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromChar(char chr, Locale loc) {
		return NumberFormat.getNumberInstance(loc).format(chr);
	}

	/**
	 * Convert from a <code>char</code>.
	 *
	 * @return A string.
	 * @param chr The <code>char</code> to convert.
	 */
	public static String fmtFromChar(char chr) {
		return fmtFromChar(chr, Locale.getDefault());
	}

	/**
	 * Returns the mapped date format item.
	 *
	 * @param loc The locale.
	 * @return The date format item.
	 */
	private static FormatItem getDateFormatItem(Locale loc) {
		FormatItem item = getDateFormatMap().get(loc);
		if (item == null) {
			SimpleDateFormat simpleFormat = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, loc);
			String pattern = getNormalizedPattern(simpleFormat.toPattern());
			simpleFormat.applyPattern(pattern);
			item = new FormatItem();
			item.format = simpleFormat;
			item.pattern = pattern;
			getDateFormatMap().put(loc, item);
		}
		return item;
	}

	/**
	 * Returns the mapped time format item.
	 *
	 * @param loc The locale.
	 * @return The time format item.
	 */
	private static FormatItem getTimeFormatItem(Locale loc) {
		FormatItem item = getTimeFormatMap().get(loc);
		if (item == null) {
			SimpleDateFormat simpleFormat = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.MEDIUM, loc);
			String pattern = getNormalizedPattern(simpleFormat.toPattern());
			simpleFormat.applyPattern(pattern);
			item = new FormatItem();
			item.format = simpleFormat;
			item.pattern = pattern;
			getTimeFormatMap().put(loc, item);
		}
		return item;
	}

	/**
	 * Returns the mapped timestamp format item.
	 *
	 * @param loc The locale.
	 * @return The timestamp format item.
	 */
	private static FormatItem getTimestampFormatItem(Locale loc) {
		FormatItem item = getTimestampFormatMap().get(loc);
		if (item == null) {
			SimpleDateFormat simpleFormat = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, loc);
			String pattern = getNormalizedPattern(simpleFormat.toPattern());
			simpleFormat.applyPattern(pattern);
			item = new FormatItem();
			item.format = simpleFormat;
			item.pattern = pattern;
			getTimestampFormatMap().put(loc, item);
		}
		return item;
	}

	/**
	 * Returns the date format map.
	 *
	 * @return The date format map.
	 */
	private static HashMap<Locale, FormatItem> getDateFormatMap() {
		if (dateFormatMap == null) {
			dateFormatMap = new HashMap<>();
		}
		return dateFormatMap;
	}

	/**
	 * Returns the time format map.
	 *
	 * @return The time format map.
	 */
	private static HashMap<Locale, FormatItem> getTimeFormatMap() {
		if (timeFormatMap == null) {
			timeFormatMap = new HashMap<>();
		}
		return timeFormatMap;
	}

	/**
	 * Returns the timestamp format map.
	 *
	 * @return The timestamp format map.
	 */
	private static HashMap<Locale, FormatItem> getTimestampFormatMap() {
		if (timestampFormatMap == null) {
			timestampFormatMap = new HashMap<>();
		}
		return timestampFormatMap;
	}

	/**
	 * Returns the localized date format.
	 *
	 * @param loc The locale.
	 * @return The localized date format.
	 */
	public static SimpleDateFormat getDateFormat(Locale loc) {
		return getDateFormatItem(loc).format;
	}

	/**
	 * Returns the localized time format.
	 *
	 * @param loc The locale.
	 * @return The localized time format.
	 */
	public static SimpleDateFormat getTimeFormat(Locale loc) {
		return getTimeFormatItem(loc).format;
	}

	/**
	 * Returns the localized timestamp format.
	 *
	 * @param loc The locale.
	 * @return The localized timestamp format.
	 */
	public static SimpleDateFormat getTimestampFormat(Locale loc) {
		return getTimestampFormatItem(loc).format;
	}

	/**
	 * Returns the localized pattern.
	 *
	 * @param loc The locale.
	 * @return The localized pattern.
	 */
	public static String getNormalizedDatePattern(Locale loc) {
		return getDateFormatItem(loc).pattern;
	}

	/**
	 * Returns the localized pattern.
	 *
	 * @param loc The locale.
	 * @return The localized pattern.
	 */
	public static String getNormalizedTimePattern(Locale loc) {
		return getTimeFormatItem(loc).pattern;
	}

	/**
	 * Returns the localized pattern.
	 *
	 * @param loc The locale.
	 * @return The localized pattern.
	 */
	public static String getNormalizedTimestampPattern(Locale loc) {
		return getTimestampFormatItem(loc).pattern;
	}

	/**
	 * Convert from a <code>Date</code>.
	 *
	 * @return A string.
	 * @param date The <code>Date</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromDate(java.sql.Date date, Locale loc) {
		if (date == null) {
			return "";
		}
		return getDateFormat(loc).format(date);
	}

	/**
	 * Convert from a <code>Date</code>.
	 *
	 * @return A string.
	 * @param date The <code>Date</code> to convert.
	 */
	public static String fmtFromDate(java.sql.Date date) {
		return fmtFromDate(date, Locale.getDefault());
	}

	/**
	 * Convert from a <code>double</code>.
	 *
	 * @return A string.
	 * @param num The <code>double</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromDouble(double num, Locale loc) {
		return java.text.NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from a <code>double</code>.
	 *
	 * @return A string.
	 * @param num The <code>double</code> to convert.
	 */
	public static String fmtFromDouble(double num) {
		return fmtFromDouble(num, Locale.getDefault());
	}

	/**
	 * Convert from a <code>float</code>.
	 *
	 * @return A string.
	 * @param num The <code>float</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromFloat(float num, Locale loc) {
		return java.text.NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from a <code>float</code>.
	 *
	 * @return A string.
	 * @param num The <code>float</code> to convert.
	 */
	public static String fmtFromFloat(float num) {
		return fmtFromFloat(num, Locale.getDefault());
	}

	/**
	 * Convert from an <code>int</code>.
	 *
	 * @return A string.
	 * @param num The <code>int</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromInt(int num, Locale loc) {
		return java.text.NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from an <code>int</code>.
	 *
	 * @return A string.
	 * @param num The <code>int</code> to convert.
	 */
	public static String fmtFromInt(int num) {
		return fmtFromInt(num, Locale.getDefault());
	}

	/**
	 * Convert from a <code>long</code>.
	 *
	 * @return A string.
	 * @param num The <code>long</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromLong(long num, Locale loc) {
		return java.text.NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from a <code>long</code>.
	 *
	 * @return A string.
	 * @param num The <code>long</code> to convert.
	 */
	public static String fmtFromLong(long num) {
		return fmtFromLong(num, Locale.getDefault());
	}

	/**
	 * Convert from a <code>short</code>.
	 *
	 * @return A string.
	 * @param num The <code>short</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromShort(short num, Locale loc) {
		return java.text.NumberFormat.getNumberInstance(loc).format(num);
	}

	/**
	 * Convert from a <code>short</code>.
	 *
	 * @return A string.
	 * @param num The <code>short</code> to convert.
	 */
	public static String fmtFromShort(short num) {
		return fmtFromShort(num, Locale.getDefault());
	}

	/**
	 * Convert from a <code>Time</code>.
	 *
	 * @return A string.
	 * @param time The <code>Time</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromTime(java.sql.Time time, Locale loc) {
		if (time == null) {
			return "";
		}
		return getTimeFormat(loc).format(time);
	}

	/**
	 * Convert from a <code>Time</code>.
	 *
	 * @return A string.
	 * @param time The <code>Time</code> to convert.
	 */
	public static String fmtFromTime(java.sql.Time time) {
		return fmtFromTime(time, Locale.getDefault());
	}

	/**
	 * Convert from a <code>Timestamp</code>.
	 *
	 * @return A string.
	 * @param time The <code>Timestamp</code> to convert.
	 * @param loc The locale to apply.
	 */
	public static String fmtFromTimestamp(java.sql.Timestamp time, Locale loc) {
		if (time == null) {
			return "";
		}
		return getTimestampFormat(loc).format(time);
	}

	/**
	 * Convert from a <code>Timestamp</code>.
	 *
	 * @return A string.
	 * @param time The <code>Timestamp</code> to convert.
	 */
	public static String fmtFromTimestamp(java.sql.Timestamp time) {
		return fmtFromTimestamp(time, Locale.getDefault());
	}

	/**
	 * Convert to <code>BigDecimal</code> from a formatted string.
	 *
	 * @return A <code>BigDecimal</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static BigDecimal fmtToBigDecimal(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return new BigDecimal(java.text.NumberFormat.getNumberInstance(loc).parse(str).doubleValue());
	}

	/**
	 * Convert to <code>BigDecimal</code> from a formatted string.
	 *
	 * @return A <code>BigDecimal</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static BigDecimal fmtToBigDecimal(String str) throws ParseException {
		return fmtToBigDecimal(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>boolean</code> from a formatted string.
	 *
	 * @return A <code>boolean</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 */
	public static boolean fmtToBoolean(String str, Locale loc) {
		if (loc.getLanguage().equalsIgnoreCase("es")) {
			return (str.equalsIgnoreCase("Si"));
		}
		if (loc.getLanguage().equalsIgnoreCase("fr")) {
			return (str.equalsIgnoreCase("Oui"));
		}
		if (loc.getLanguage().equalsIgnoreCase("pt")) {
			return (str.equalsIgnoreCase("Sim"));
		}
		if (loc.getLanguage().equalsIgnoreCase("de")) {
			return (str.equalsIgnoreCase("Ja"));
		}
		if (loc.getLanguage().equalsIgnoreCase("pl")) {
			return (str.equalsIgnoreCase("Tak"));
		}
		return (str.equalsIgnoreCase("Yes"));
	}

	/**
	 * Convert to <code>boolean</code> from a formatted string.
	 *
	 * @return A <code>boolean</code>
	 * @param str The formatted string to convert.
	 */
	public static boolean fmtToBoolean(String str) {
		return fmtToBoolean(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>byte</code> from a formatted string.
	 *
	 * @return A <code>byte</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static byte fmtToByte(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return NumberFormat.getNumberInstance(loc).parse(str).byteValue();
	}

	/**
	 * Convert to <code>byte</code> from a formatted string.
	 *
	 * @return A <code>byte</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static byte fmtToByte(String str) throws ParseException {
		return fmtToByte(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>char</code> from a formatted string.
	 *
	 * @return A <code>char</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static char fmtToChar(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return (char) java.text.NumberFormat.getNumberInstance(loc).parse(str).intValue();
	}

	/**
	 * Convert to <code>char</code> from a formatted string.
	 *
	 * @return A <code>char</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static char fmtToChar(String str) throws ParseException {
		return fmtToChar(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>Date</code> from a formatted string.
	 *
	 * @return A <code>Date</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static Date fmtToDate(String str, Locale loc) throws ParseException {
		try {
			if (str == null) {
				return null;
			}
			if (str.trim().length() == 0) {
				return null;
			}
			java.util.Date date = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT, loc).parse(str);
			return new Date(date.getTime());
		} catch (ParseException e) {
			return null;
		}
	}

	/**
	 * Convert to <code>Date</code> from a formatted string.
	 *
	 * @return A <code>Date</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static Date fmtToDate(String str) throws ParseException {
		return fmtToDate(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>double</code> from a formatted string.
	 *
	 * @return A <code>double</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static double fmtToDouble(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return java.text.NumberFormat.getNumberInstance(loc).parse(str).doubleValue();
	}

	/**
	 * Convert to <code>double</code> from a formatted string.
	 *
	 * @return A <code>double</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static double fmtToDouble(String str) throws ParseException {
		return fmtToDouble(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>float</code> from a formatted string.
	 *
	 * @return A <code>float</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static float fmtToFloat(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return java.text.NumberFormat.getNumberInstance(loc).parse(str).floatValue();
	}

	/**
	 * Convert to <code>float</code> from a formatted string.
	 *
	 * @return A <code>float</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static float fmtToFloat(String str) throws ParseException {
		return fmtToFloat(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>int</code> from a formatted string.
	 *
	 * @return An <code>int</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static int fmtToInt(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return java.text.NumberFormat.getNumberInstance(loc).parse(str).intValue();
	}

	/**
	 * Convert to <code>int</code> from a formatted string.
	 *
	 * @return An <code>int</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static int fmtToInt(String str) throws ParseException {
		return fmtToInt(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>long</code> from a formatted string.
	 *
	 * @return A <code>long</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static long fmtToLong(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return java.text.NumberFormat.getNumberInstance(loc).parse(str).longValue();
	}

	/**
	 * Convert to <code>long</code> from a formatted string.
	 *
	 * @return A <code>long</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static long fmtToLong(String str) throws ParseException {
		return fmtToLong(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>short</code> from a formatted string.
	 *
	 * @return A <code>short</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 * @throws java.text.ParseException
	 */
	public static short fmtToShort(String str, Locale loc) throws ParseException {
		if (str.length() == 0) {
			str = "0";
		}
		return java.text.NumberFormat.getNumberInstance(loc).parse(str).shortValue();
	}

	/**
	 * Convert to <code>short</code> from a formatted string.
	 *
	 * @return A <code>short</code>
	 * @param str The formatted string to convert.
	 * @throws java.text.ParseException
	 */
	public static short fmtToShort(String str) throws ParseException {
		return fmtToShort(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>Time</code> from a formatted string (##:##:##), using the normalized pattern.
	 *
	 * @return A <code>Time</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 */
	public static Time fmtToTime(String str, Locale loc) {
		return fmtToTime(str, getNormalizedTimePattern(loc));
	}

	/**
	 * Convert to <code>Time</code> using the given pattern.
	 *
	 * @return A <code>Time</code>
	 * @param str The formatted string to convert.
	 * @param timePattern The Time pattern to apply.
	 */
	public static Time fmtToTime(String str, String timePattern) {
		String time = str;
		if ((time == null) || (time.trim().length() == 0)) {
			return null;
		}
		if (time.length() == 5) {
			time = addSecondsText(time);
		}
		try {
			java.util.Date date = new SimpleDateFormat(timePattern).parse(time);
			return new Time(date.getTime());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Convert to <code>Time</code> from a formatted string.
	 *
	 * @return A <code>Time</code>
	 * @param str The formatted string to convert.
	 */
	public static Time fmtToTime(String str) {
		return fmtToTime(str, Locale.getDefault());
	}

	/**
	 * Convert to <code>Timestamp</code> from a formatted string, using the normalized pattern.
	 *
	 * @return A <code>Timestamp</code>
	 * @param str The formatted string to convert.
	 * @param loc The locale to apply.
	 */
	public static Timestamp fmtToTimestamp(String str, Locale loc) {
		return fmtToTimestamp(str, getNormalizedTimestampPattern(loc));
	}

	/**
	 * Convert to <code>Timestamp</code> using the given Timestamp pattern.
	 *
	 * @return A <code>Timestamp</code>
	 * @param str The formatted string to convert.
	 * @param timestampPattern The Timestamp pattern to apply.
	 */
	public static Timestamp fmtToTimestamp(String str, String timestampPattern) {
		String time = str;
		if ((time == null) || (time.trim().length() == 0)) {
			return null;
		}
		if (time.length() == 16) {
			time = addSecondsText(time);
		}
		try {
			java.util.Date date = new SimpleDateFormat(timestampPattern).parse(time);
			return new Timestamp(date.getTime());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Convert to <code>Timestamp</code> from a formatted string.
	 *
	 * @return A <code>Timestamp</code>
	 * @param str The formatted string to convert.
	 */
	public static Timestamp fmtToTimestamp(String str) {
		return fmtToTimestamp(str, Locale.getDefault());
	}

	/**
	 * Returns the normalized pattern for a date or time. The normalized pattern is always two digit long for days and
	 * months and four digit long for the year.
	 *
	 * @return The normalized pattern.
	 * @param pattern The original pattern.
	 */
	public static String getNormalizedPattern(String pattern) {
		StringBuilder buffer = new StringBuilder();
		int index = 0;
		int length = pattern.length();
		while (index < length) {
			char c = pattern.charAt(index);
			char origChar = c;
			if (c == 'a') {
				index++;
				continue;
			}
			if (c == 'k' || c == 'K' || c == 'h') {
				c = 'H';
			}
			if (c == 'd' || c == 'M' || c == 'y' || c == 'H' || c == 'm' || c == 's') {
				switch (c) {
					case 'd':
						buffer.append("dd");
						break;
					case 'M':
						buffer.append("MM");
						break;
					case 'y':
						buffer.append("yyyy");
						break;
					case 'H':
						buffer.append("HH");
						break;
					case 'm':
						buffer.append("mm");
						break;
					case 's':
						buffer.append("ss");
						break;
				}
				while (index < length && pattern.charAt(index) == origChar) {
					index++;
				}
				continue;
			}
			buffer.append(c);
			index++;
		}
		return buffer.toString().trim();
	}

	/**
	 * Convert a <code>BigDecimal</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>BigDecimal</code>.
	 */
	public static String unfFromBigDecimal(BigDecimal n) {
		if (n == null) {
			return "";
		}
		return n.toString();
	}

	/**
	 * Convert a <code>boolean</code> to the unformatted form (true/false).
	 *
	 * @return A string.
	 * @param b A <code>boolean</code>.
	 */
	public static String unfFromBoolean(boolean b) {
		return (b ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
	}

	/**
	 * Convert a <code>byte</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>byte</code>.
	 */
	public static String unfFromByte(byte n) {
		return Byte.toString(n);
	}

	/**
	 * Convert a <code>char</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>char</code>.
	 */
	public static String unfFromChar(char n) {
		return (new Character(n)).toString();
	}

	/**
	 * Convert a <code>Date</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param d A <code>Date</code>.
	 */
	public static String unfFromDate(java.sql.Date d) {
		if (d == null) {
			return "";
		}
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
		SimpleDateFormat ef = new SimpleDateFormat("G");
		String sdate = df.format(d);
		String sera = ef.format(d);
		return (sera.equals("BC") ? "-" + sdate : sdate);
	}

	/**
	 * Convert a <code>double</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>double</code>.
	 */
	public static String unfFromDouble(double n) {
		return Double.toString(n);
	}

	/**
	 * Convert a <code>float</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>float</code>.
	 */
	public static String unfFromFloat(float n) {
		return Float.toString(n);
	}

	/**
	 * Convert an <code>int</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n An <code>int</code>.
	 */
	public static String unfFromInt(int n) {
		return Integer.toString(n);
	}

	/**
	 * Convert a <code>long</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>long</code>.
	 */
	public static String unfFromLong(long n) {
		return Long.toString(n);
	}

	/**
	 * Convert a <code>short</code> to the unformatted form.
	 *
	 * @return A string.
	 * @param n A <code>short</code>.
	 */
	public static String unfFromShort(short n) {
		return Short.toString(n);
	}

	/**
	 * Convert from a <code>Time</code> to an unlocalized string with the format <code>hhmmss</code>.
	 *
	 * @return The string.
	 * @param t A <code>Time</code>.
	 */
	public static String unfFromTime(java.sql.Time t) {
		return unfFromTime(t, false);
	}

	/**
	 * Convert from a <code>Time</code> to an unlocalized string with the format <code>hhmmss</code> or
	 * <code>hhmmssnnn</code>.
	 *
	 * @return The string.
	 * @param time A <code>Time</code>.
	 * @param millis A <code>boolean</code> to include milliseconds.
	 */
	public static String unfFromTime(java.sql.Time time, boolean millis) {
		if (time == null) {
			return "";
		}
		StringBuilder pattern = new StringBuilder();
		pattern.append("HHmmss");
		if (millis) {
			pattern.append("SSS");
		}
		SimpleDateFormat df = new SimpleDateFormat(pattern.toString());
		return df.format(time);
	}

	/**
	 * Convert from a <code>Timestamp</code> to an unlocalized string with the format
	 * <b>yyyymmddhhmmss</b>.
	 *
	 * @return The string.
	 * @param t A <code>Timestamp</code>.
	 */
	public static String unfFromTimestamp(java.sql.Timestamp t) {
		return unfFromTimestamp(t, true, false);
	}

	/**
	 * Convert from a <code>Timestamp</code> to an unlocalized string with the format <code>yyyymmddhhmmss</code> or
	 * <code>yyyymmddhhmmssnnn</code>
	 *
	 * @return The string.
	 * @param timestamp A <code>Timestamp</code>.
	 * @param millis A <code>boolean</code> to include milliseconds.
	 */
	public static String unfFromTimestamp(java.sql.Timestamp timestamp, boolean millis) {
		return unfFromTimestamp(timestamp, millis, false);
	}

	/**
	 * Convert from a <code>Timestamp</code> to an unlocalized string with the format <code>yyyymmddhhmmss</code> or
	 * <code>yyyymmddhhmmssnnn</code>
	 *
	 * @return The string.
	 * @param timestamp A <code>Timestamp</code>.
	 * @param millis A <code>boolean</code> to include milliseconds.
	 * @param separators A boolean to include standard separators
	 */
	public static String unfFromTimestamp(java.sql.Timestamp timestamp, boolean millis, boolean separators) {
		if (timestamp == null) {
			return "";
		}
		StringBuilder pattern = new StringBuilder();
		pattern.append("yyyy");
		if (separators) {
			pattern.append("-");
		}
		pattern.append("MM");
		if (separators) {
			pattern.append("-");
		}
		pattern.append("dd");
		if (separators) {
			pattern.append(" ");
		}
		pattern.append("HH");
		if (separators) {
			pattern.append(":");
		}
		pattern.append("mm");
		if (separators) {
			pattern.append(":");
		}
		pattern.append("ss");
		if (millis) {
			if (separators) {
				pattern.append(".");
			}
			pattern.append("SSS");
		}
		SimpleDateFormat df = new SimpleDateFormat(pattern.toString());
		return df.format(timestamp);
	}

	public final static String TIMESTAMP_SUFFIX_PATTERN = "yyyyMMdd-HHmmss-SSS";

	/**
	 * Convert from a <code>Timestamp</code> to an unlocalized string with the format <code>yyyymmddhhmmss</code> or
	 * <code>yyyymmddhhmmssnnn</code>
	 *
	 * @return The string.
	 * @param timestamp A <code>Timestamp</code>.
	 */
	public static String unfFromTimestampSuffix(java.sql.Timestamp timestamp) {
		if (timestamp == null) {
			return "";
		}
		StringBuilder pattern = new StringBuilder();
		pattern.append(TIMESTAMP_SUFFIX_PATTERN);
		SimpleDateFormat df = new SimpleDateFormat(pattern.toString());
		return df.format(timestamp);
	}

	/**
	 * Convert a <code>Timestamp</code> from an string with the format <code>yyyyMMdd-HHmmss-SSS</code> of a timesatmp
	 * suffix.
	 *
	 * @return The <code>Timestamp</code>.
	 * @param str The string to convert.
	 */
	public static Timestamp unfToTimestampSuffix(String str) {
		if (str == null || str.length() == 0) {
			return null;
		}
		try {
			int year = Integer.parseInt(str.substring(0, 4));
			int month = Integer.parseInt(str.substring(4, 6));
			int day = Integer.parseInt(str.substring(6, 8));
			int hour = Integer.parseInt(str.substring(9, 11));
			int min = Integer.parseInt(str.substring(11, 13));
			int sec = Integer.parseInt(str.substring(13, 15));
			int msec = Integer.parseInt(str.substring(16, 19));
			return Calendar.createTimestamp(year, month, day, hour, min, sec, msec);
		} catch (Exception e) {
			throw new IllegalArgumentException("Invalid unf timestamp " + str);
		}
	}

	/**
	 * Convert an unformatted string to <code>BigDecimal</code>.
	 *
	 * @return A <code>BigDecimal</code>
	 * @param str The string to convert.
	 */
	public static BigDecimal unfToBigDecimal(String str) {
		if (str == null || str.length() == 0) {
			str = "0";
		}
		BigDecimal b = new BigDecimal(str);
		return b.setScale(b.scale(), BigDecimal.ROUND_HALF_UP);
	}

	/**
	 * Convert a unformatted string to a <code>boolean</code>.
	 *
	 * @return A <code>boolean</code>.
	 * @param str The string to convert.
	 */
	public static boolean unfToBoolean(String str) {
		return (str.equals("true"));
	}

	/**
	 * Convert an unformatted string to <code>byte</code>.
	 *
	 * @return A <code>byte</code>
	 * @param str The string to convert.
	 */
	public static byte unfToByte(String str) {
		try {
			return Byte.parseByte(str);
		} catch (Exception e) {
			return (byte) 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>char</code>.
	 *
	 * @return A <code>char</code>
	 * @param str The string to convert.
	 */
	public static char unfToChar(String str) {
		try {
			return (char) Integer.parseInt(str);
		} catch (Exception e) {
			return (char) 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>Date</code>.
	 *
	 * @return A <code>Date</code>
	 * @param str The string to convert.
	 */
	public static Date unfToDate(String str) {
		try {
			java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat("yyyyMMdd");
			return new Date(fmt.parse(str).getTime());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Convert an unformatted string to <code>double</code>.
	 *
	 * @return A <code>double</code>
	 * @param str The string to convert.
	 */
	public static double unfToDouble(String str) {
		try {
			return Double.parseDouble(str);
		} catch (Exception e) {
			return 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>float</code>.
	 *
	 * @return A <code>float</code>
	 * @param str The string to convert.
	 */
	public static float unfToFloat(String str) {
		try {
			return Float.parseFloat(str);
		} catch (Exception e) {
			return 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>int</code>.
	 *
	 * @return An <code>int</code>
	 * @param str The string to convert.
	 */
	public static int unfToInt(String str) {
		try {
			return Integer.parseInt(str);
		} catch (Exception e) {
			return 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>long</code>.
	 *
	 * @return A <code>long</code>
	 * @param str The string to convert.
	 */
	public static long unfToLong(String str) {
		try {
			return Long.parseLong(str);
		} catch (Exception e) {
			return 0;
		}
	}

	/**
	 * Convert an unformatted string to <code>short</code>.
	 *
	 * @return A <code>short</code>
	 * @param str The string to convert.
	 */
	public static short unfToShort(String str) {
		try {
			return Short.parseShort(str);
		} catch (Exception e) {
			return (short) 0;
		}
	}

	/**
	 * Convert a <code>Time</code> from an unlocalized string with the format <code>hhmmss</code>.
	 *
	 * @return The <code>Time</code>.
	 * @param str The string to convert.
	 */
	public static Time unfToTime(String str) {
		return unfToTime(str, false);
	}

	/**
	 * Convert a <code>Time</code> from an unlocalized string with the format <code>hhmmss</code> or
	 * <code>hhmmssnnn</code>.
	 *
	 * @return The <code>Time</code>.
	 * @param str The string to convert.
	 * @param millis A <code>boolean</code> indicating if the string contains millisecond data.
	 */
	public static Time unfToTime(String str, boolean millis) {
		try {
			int hour = Integer.parseInt(str.substring(0, 2));
			int min = Integer.parseInt(str.substring(2, 4));
			if (millis) {
				int sec = Integer.parseInt(str.substring(4, 6));
				int msec = Integer.parseInt(str.substring(6, 9));
				return Calendar.createTime(hour, min, sec, msec);
			} else {
				int sec = Integer.parseInt(str.substring(4));
				return Calendar.createTime(hour, min, sec);
			}
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Convert a <code>Timestamp</code> from an unlocalized string with the format <code>yyyymmddhhmmss</code> or
	 * <code>yyyymmddhhmmssnnn</code> or <code>yyyy-mm-dd hh:mm:ss</code> or <code>yyyy-mm-dd hh:mm:ss.nnn</code>.
	 *
	 * @return The <code>Timestamp</code>.
	 * @param str The string to convert.
	 */
	public static Timestamp unfToTimestamp(String str) {
		if (str == null || str.length() == 0) {
			return null;
		}
		try {
			if (str.length() == 23) {
				int year = Integer.parseInt(str.substring(0, 4));
				int month = Integer.parseInt(str.substring(5, 7));
				int day = Integer.parseInt(str.substring(8, 10));
				int hour = Integer.parseInt(str.substring(11, 13));
				int min = Integer.parseInt(str.substring(14, 16));
				int sec = Integer.parseInt(str.substring(17, 19));
				int msec = Integer.parseInt(str.substring(20, 23));
				return Calendar.createTimestamp(year, month, day, hour, min, sec, msec);
			} else if (str.length() == 19) {
				int year = Integer.parseInt(str.substring(0, 4));
				int month = Integer.parseInt(str.substring(5, 7));
				int day = Integer.parseInt(str.substring(8, 10));
				int hour = Integer.parseInt(str.substring(11, 13));
				int min = Integer.parseInt(str.substring(14, 16));
				int sec = Integer.parseInt(str.substring(17, 19));
				return Calendar.createTimestamp(year, month, day, hour, min, sec);
			} else if (str.length() == 17) {
				int year = Integer.parseInt(str.substring(0, 4));
				int month = Integer.parseInt(str.substring(4, 6));
				int day = Integer.parseInt(str.substring(6, 8));
				int hour = Integer.parseInt(str.substring(8, 10));
				int min = Integer.parseInt(str.substring(10, 12));
				int sec = Integer.parseInt(str.substring(12, 14));
				int msec = Integer.parseInt(str.substring(14, 17));
				return Calendar.createTimestamp(year, month, day, hour, min, sec, msec);
			} else if (str.length() == 14) {
				int year = Integer.parseInt(str.substring(0, 4));
				int month = Integer.parseInt(str.substring(4, 6));
				int day = Integer.parseInt(str.substring(6, 8));
				int hour = Integer.parseInt(str.substring(8, 10));
				int min = Integer.parseInt(str.substring(10, 12));
				int sec = Integer.parseInt(str.substring(12, 14));
				return Calendar.createTimestamp(year, month, day, hour, min, sec);
			} else {
				throw new Exception();
			}
		} catch (Exception e) {
			throw new IllegalArgumentException("Invalid unf timestamp " + str);
		}
	}

	/**
	 * Converts unicodes to encoded \\uxxxx and writes out any of the characters in specialSaveChars with a preceding
	 * slash
	 *
	 * @param theString The string to convert.
	 * @return The encoded string
	 */
	public static String toEncoded(String theString) {
		if (theString == null) {
			return new String();
		}
		char aChar;
		int len = theString.length();
		StringBuilder outBuffer = new StringBuilder(len * 2);

		for (int x = 0; x < len;) {
			aChar = theString.charAt(x++);
			switch (aChar) {
				case '\\':
					outBuffer.append('\\');
					outBuffer.append('\\');
					continue;
				case '\t':
					outBuffer.append('\\');
					outBuffer.append('t');
					continue;
				case '\n':
					outBuffer.append('\\');
					outBuffer.append('n');
					continue;
				case '\r':
					outBuffer.append('\\');
					outBuffer.append('r');
					continue;
				case '\f':
					outBuffer.append('\\');
					outBuffer.append('f');
					continue;
				default:
					if ((aChar < 20) || (aChar > 127)) {
						outBuffer.append('\\');
						outBuffer.append('u');
						outBuffer.append(toHex((aChar >> 12) & 0xF));
						outBuffer.append(toHex((aChar >> 8) & 0xF));
						outBuffer.append(toHex((aChar >> 4) & 0xF));
						outBuffer.append(toHex((aChar) & 0xF));
					} else {
						if (specialSaveChars.indexOf(aChar) != -1) {
							outBuffer.append('\\');
						}
						outBuffer.append(aChar);
					}
			}
		}
		return outBuffer.toString();
	}

	/**
	 * Converts encoded \\uxxxx to unicode chars and changes special saved chars to their original forms
	 *
	 * @param theString The string to convert.
	 * @return The unicode encoded string
	 */
	public static String toUnicode(String theString) {
		char aChar;
		int len = theString.length();
		StringBuilder outBuffer = new StringBuilder(len);

		for (int x = 0; x < len;) {
			aChar = theString.charAt(x++);
			if (aChar == '\\') {
				aChar = theString.charAt(x++);
				if (aChar == 'u') {
					// Read the xxxx
					int value = 0;
					for (int i = 0; i < 4; i++) {
						aChar = theString.charAt(x++);
						switch (aChar) {
							case '0':
							case '1':
							case '2':
							case '3':
							case '4':
							case '5':
							case '6':
							case '7':
							case '8':
							case '9':
								value = (value << 4) + aChar - '0';
								break;
							case 'a':
							case 'b':
							case 'c':
							case 'd':
							case 'e':
							case 'f':
								value = (value << 4) + 10 + aChar - 'a';
								break;
							case 'A':
							case 'B':
							case 'C':
							case 'D':
							case 'E':
							case 'F':
								value = (value << 4) + 10 + aChar - 'A';
								break;
							default:
								throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
						}
					}
					outBuffer.append((char) value);
				} else {
					if (aChar == 't') {
						aChar = '\t';
					} else if (aChar == 'r') {
						aChar = '\r';
					} else if (aChar == 'n') {
						aChar = '\n';
					} else if (aChar == 'f') {
						aChar = '\f';
					}
					outBuffer.append(aChar);
				}
			} else {
				outBuffer.append(aChar);
			}
		}
		return outBuffer.toString();
	}

	/**
	 * Convert a nibble to a hex character
	 *
	 * @param	nibble	the nibble to convert.
	 * @return
	 */
	public static char toHex(int nibble) {
		return hexDigit[(nibble & 0xF)];
	}
}
